﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using OverStore.Runtime.ObjectRepository.CallbackManager;
using OverStore.Runtime.OverStoreContext;
using OverStore.Runtime.PersistenceServiceProvider;

namespace OverStore.Configuration.ObjectRepository.CallbackManager
{
    /// <summary>
    /// Configuration for <see cref="ICallbackManager{TInstance}"/> component.
    /// </summary>
    /// <typeparam name="TInstance">The type of instance.</typeparam>
    public sealed class DelegateCallbackManagerConfiguration<TInstance> : IComponentConfiguration<ICallbackManager<TInstance>>
        where TInstance : class
    {
        /// <summary>
        /// Gets or sets list of callback delegates.
        /// </summary>
        public IEnumerable<Func<TInstance, PersistenceOperation, IPersistenceServiceProvider, bool>> CallbackDelegates { get; set; }

        /// <summary>
        /// Creates the new instance of <typeparamref name="TComponent"/> type using set of services provided by <paramref name="context"/> object.
        /// </summary>
        /// <param name="context">The context provides access to services shared between components.</param>
        /// <returns>
        /// The new instance of <typeparamref name="TComponent"/> class.
        /// </returns>
        public ICallbackManager<TInstance> CreateComponent(IOverStoreContext context)
        {
            if (Object.ReferenceEquals(context, null))
                throw new ArgumentNullException("context");
            return new DelegateCallbackManager<TInstance>(
                            context.PersistenceServiceProvider,
                            CallbackDelegates);
        }

        /// <summary>
        /// Add callback for the instance of specified type. This callback maintains reference is contained in the instance.
        /// Callback saves, adds and marks reference as deleted.
        /// </summary>
        public DelegateCallbackManagerConfiguration<TInstance> Reference<TReferenceType>(Func<TInstance, TReferenceType> getReference)
            where TReferenceType : class
        {
            var container = new ReferenceContainer<TReferenceType>(getReference);
            return AddCallback(container.Callback);
        }

        /// <summary>
        /// Adds the callback method to current configuration.
        /// </summary>
        public DelegateCallbackManagerConfiguration<TInstance> AddCallback(Func<TInstance, PersistenceOperation, IPersistenceServiceProvider, bool> callback)
        {
            if (Object.ReferenceEquals(callback, null))
                throw new ArgumentNullException("callback");
            var list = (CallbackDelegates ?? Enumerable.Empty<Func<TInstance, PersistenceOperation, IPersistenceServiceProvider, bool>>()).ToList();
            list.Add(callback);
            CallbackDelegates = list;
            return this;
        }

        private class ReferenceContainer<TReference>
            where TReference : class
        {
            private readonly Func<TInstance, TReference> _GetReference;

            public ReferenceContainer(Func<TInstance, TReference> getReference)
            {
                if (Object.ReferenceEquals(getReference, null))
                    throw new ArgumentNullException("getReference");
                _GetReference = getReference;
            }

            public bool Callback(TInstance instance, PersistenceOperation operation, IPersistenceServiceProvider provider)
            {
                var reference = _GetReference(instance);
                if (reference != null)
                {
                    switch (operation)
                    {
                        case PersistenceOperation.Loaded:
                            provider.Reload(reference);
                            break;
                        case PersistenceOperation.Saved:
                            provider.Save(reference);
                            break;
                        case PersistenceOperation.MarkingAsDeleted:
                            provider.MarkAsDeleted(reference);
                            break;
                        case PersistenceOperation.Adding:
                            provider.Add(reference);
                            break;
                    }
                }
                return true;
            }
        }
    }
}
